//
// Created by jaesonzhang on 2024/9/20.
//
#include "../quickjs/quickjs.h"
#define countof(x) (sizeof(x) / sizeof((x)[0]))

JSClassID js_context_class_id;

static void js_context_finalizer(JSRuntime *rt, JSValue val) {
  auto *s = static_cast<skiacanvas::Context *>(JS_GetOpaque(val,
                                                            js_context_class_id));
  s->~Context();
  js_free_rt(rt, s);
}

static JSValue js_context_ctor(JSContext *ctx,
                               JSValueConst new_target,
                               int argc, JSValueConst *argv) {
  JSValue obj = JS_UNDEFINED;
  JSValue proto;
  int32_t contextId = 0;
  if (JS_ToInt32(ctx, &contextId, argv[0])) {
    return JS_EXCEPTION;
  }
  auto *jsApp = static_cast<JSApp *>(JS_GetContextOpaque(ctx));
  if (!jsApp) {
    return JS_EXCEPTION;
  }
  uint32_t w = jsApp->getWidth();
  uint32_t h = jsApp->getHeight();
  if (argc >= 2) {
    if (JS_ToUint32(ctx, &w, argv[0]) && JS_ToUint32(ctx, &h, argv[1])) {
    } else {
      w = jsApp->getWidth();
      h = jsApp->getHeight();
    }
  }
  void *buffer = js_malloc(ctx, sizeof(skiacanvas::Context));
  auto context = new(buffer)skiacanvas::Context(w, h);

  /* using new_target to get the prototype is necessary when the
     class is extended. */
  proto = JS_GetPropertyStr(ctx, new_target, "prototype");
  if (JS_IsException(proto))
    return JS_EXCEPTION;
  obj = JS_NewObjectProtoClass(ctx, proto, js_context_class_id);
  JS_FreeValue(ctx, proto);
  if (JS_IsException(obj)) {
    JS_FreeValue(ctx, obj);
    return JS_EXCEPTION;
  }
  JS_SetOpaque(obj, buffer);
  return obj;
}

static JSValue js_context_fill_text(JSContext *ctx, JSValueConst this_val,
                                    int argc, JSValueConst *argv) {
  auto *pContext = static_cast<skiacanvas::Context *>(JS_GetOpaque(this_val,
                                                                   js_context_class_id));

  if (!pContext || argc != 3)
    return JS_EXCEPTION;

  auto str = JS_ToCString(ctx, argv[0]);
  uint32_t w = 0, h = 0;
  JS_ToUint32(ctx, &w, argv[1]);
  JS_ToUint32(ctx, &h, argv[2]);
  pContext->fillText(str, w, h);
  return JS_NewBool(ctx, 1);
}

static JSValue js_context_fill_rect(JSContext *ctx, JSValueConst this_val,
                                    int argc, JSValueConst *argv) {
  auto *pContext = static_cast<skiacanvas::Context *>(JS_GetOpaque(this_val,
                                                                   js_context_class_id));

  if (!pContext || argc != 4)
    return JS_EXCEPTION;


  uint32_t sx = 0, sy = 0, ex = 0, ey = 0;
  JS_ToUint32(ctx, &sx, argv[0]);
  JS_ToUint32(ctx, &sy, argv[1]);
  JS_ToUint32(ctx, &ex, argv[2]);
  JS_ToUint32(ctx, &ey, argv[3]);
  pContext->fillRect(sx, sy, ex, ey);
  return JS_NewBool(ctx, 1);
}

static JSClassDef js_context_class = {
    "Context",
    .finalizer = js_context_finalizer,
};

static const JSCFunctionListEntry js_context_proto_funcs[] = {
    JS_CFUNC_DEF("fillText", 0, js_context_fill_text),
    JS_CFUNC_DEF("fillRect", 0, js_context_fill_rect),
    JS_CFUNC_DEF("clearRect", 0, js_context_fill_rect),
};

int js_context_init(JSContext *ctx) {
  JSValue context_proto, context_class;
  /* create the Context class */
  JS_NewClassID(&js_context_class_id);
  JS_NewClass(JS_GetRuntime(ctx), js_context_class_id, &js_context_class);

  context_proto = JS_NewObject(ctx);
  JS_SetPropertyFunctionList(ctx,
                             context_proto,
                             js_context_proto_funcs,
                             countof(js_context_proto_funcs));

  context_class =
      JS_NewCFunction2(ctx,
                       js_context_ctor,
                       "Context",
                       2,
                       JS_CFUNC_constructor,
                       0);
  /* set proto.constructor and ctor.prototype */
  JS_SetConstructor(ctx, context_class, context_proto);
  JS_SetClassProto(ctx, js_context_class_id, context_proto);

  // 设置到全局对象上
  JS_SetPropertyStr(ctx,
                    JS_GetGlobalObject(ctx),
                    "Context",
                    context_class);

  return 0;
}